/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <dependencies/includes/ink_renderer_interface.h>
#include <journal/app/constants.h>
#include <journal/models/types.h>
#include <journal/models/db/table_interfaces.h>

#ifndef DEBUG_ENABLE_MODELS
#ifdef _DEBUG
#define DEBUG_ENABLE_MODELS
#define DEBUG_MODEL_INTERFACE_DECLARATION \
    virtual void dump(bool includeChildren, std::wstring prefix) = 0;
#define DEBUG_MODEL_DECLARATION \
    void dump(bool includeChildren, std::wstring prefix);
#define DEBUG_MODEL_DUMP(obj, includeChildren, prefix) \
    obj->dump(includeChildren, prefix);
#else
#define DEBUG_MODEL_INTERFACE_DECLARATION
#define DEBUG_MODEL_DECLARATION
#define DEBUG_MODEL_DUMP
#endif
#endif

namespace journal { namespace model {

namespace b = baja;
namespace s = baja::storage;
namespace bi = baja::ink;

enum page_level
{
    page_none,          // object is unusable/dummy. no disk i/o took place. no initialization.
    page_self,          // self only.
    page_thumbnail,     // self + thumbnail.
    page_full,          // self + thumbnail + children loaded.
    page_deleted
};

struct ijournal_model
{
};

struct inotebook_model_observer : public baja::ibase
{
    BAJA_INTERFACE_NOTAG(inotebook_model_observer);

    // fired in response to the completion of a inotebook_model::createPage() call.
    // the page is new - i.e. not loaded from disk.
    // the page is ready to be used - i.e. in full load level.
    // listener: notebook view.
    virtual void onPageModelCreated(const std::shared_ptr<ipage_model>& page) = 0;

    // fired in response to the same event being fired by a page (ipage_model_observer::onPageLoadLevelSet).
    // listener: notebook view.
    virtual void onPageLoadLevelSet(const std::shared_ptr<ipage_model>& page, const page_level currentLevel) = 0;
};

struct inotebook_model : public b::ibase
{
    BAJA_INTERFACE_NOTAG(inotebook_model);

    virtual b::int32 addObserver(const std::shared_ptr<inotebook_model_observer>& observer) = 0;
    virtual void removeObserver(const std::shared_ptr<inotebook_model_observer>& observer) = 0;
    virtual void removeObserver(const b::int32 cookie) = 0;

    virtual b::uint32 storageId() = 0;

    // creates a new page and initializes it.
    // on completion: inotebook_model_observer::onPageModelCreated() is called.
    virtual b::uint32 createPageModel() = 0;

    // retrieves the count of pages the model is aware of.
    // note that this is not the number of pages in memory, rather, it is the number of pages in the journal.
    // some might be cached, and some might not be cached. a call to getPage() may follow to retrieve a 
    // point to the page.
    virtual b::uint32 pageCount() = 0;

    // retrieves a pointer to an existing page.
    // a page retrieved through getPage() may not be usable immediately. the caller has to call 
    // setLoadLevelAsync() on it first, and wait for the onLoadLevelSet() call.
    virtual std::shared_ptr<ipage_model> getPage(const b::uint32 pageNumber) = 0;

    virtual size_t getPageNumberFromPageId(const size_t pageId) = 0;
    virtual size_t getPageIdFromPageNumber(const size_t pageNumber) = 0;

    virtual void movePage(size_t fromPageNumber, size_t toPageNumber) = 0;

    DEBUG_MODEL_INTERFACE_DECLARATION;
};

struct ipage_model;

struct ipage_model_observer : public baja::ibase
{
    BAJA_INTERFACE_NOTAG(ipage_model_observer);

    // fired with/at:
    // (1) page_self / when page 'self' is loaded. it is fired only once during the life time of a page loaded from disk.
    // (2) page_thumbnail / when page 'thumbnail' is ready. it is fired every time a thumbnail is requested and ready.
    // (3) load full / when page 'all items' have been loaded. it is fired every time all items are (re)loaded into memory.
    //     when loading happens in passes, it is fired only once after loading of item and sub items are complete.
    //     note that until load full is sent, notifications of sub object being created and loaded will get fired.
    virtual void onPageLoadLevelSet(
        const std::shared_ptr<ipage_model>& page,
        const page_level currentLevel) = 0;

    // fired in response to addPhoto() or while re-creating objects for loading.
    // this indicates that the photo is in the list of photos and is ready to be displayed.
    virtual void onPhotoModelAdded(
        const std::shared_ptr<iphoto_model>& photo) = 0;

    virtual void onPhotoModelBroughtToFront(
        const std::shared_ptr<iphoto_model>& photo) = 0;

    virtual void onPhotoModelRemoved(
        const std::shared_ptr<iphoto_model>& photo) = 0;

    virtual void onInkSurfaceModelReady(
        const std::shared_ptr<iink_surface_model>& inkSurface) = 0;

    // we are not passing the thumbnail blob in the call back to avoid
    // unnecessary copying of memory blocks.
    // because of the move semantics of thumbnails, if there is more than one
    // observer, it might cause an issue if one owns the buffer through the 
    // page->thumbnail().
    virtual void onThumbnailSaved(
        const std::shared_ptr<ipage_model>& page) = 0;

    virtual void onPageModelPaperTypeChanged(
        const b::uint32& paperType) = 0;
};

struct ipage_model : public b::ibase
{
    BAJA_INTERFACE_NOTAG(ipage_model);

    virtual baja::int32 addObserver(const std::shared_ptr<ipage_model_observer>& observer) = 0;
    virtual void removeObserver(const std::shared_ptr<ipage_model_observer>& observer) = 0;
    virtual void removeObserver(const b::int32 cookie) = 0;

    virtual unsigned int storageId() = 0;
    virtual baja::uint32 pageNumber() = 0;
    virtual std::shared_ptr<inotebook_model> parent() = 0;

    virtual baja::uint32 paperType() = 0;
    virtual void setPaperType(b::uint32 paperType) = 0;

    // photo support
    virtual void addPhoto(
        const std::shared_ptr<b::storage::istorage_file>& file,
        const b::float32& scale,
        const m::point2d& position,
        const b::float32& angle
        ) = 0;

    virtual b::uint32 photoCount() = 0;

    // add a photo model to the page photo model collection.
    // on completion: ipage_model_observer::onPhotoAdded()
    // virtual void addPhoto(const std::shared_ptr<iphoto_model>& photos) = 0;
    virtual void removePhoto(const id& id) = 0;

    virtual void bringPhotoToFront(const id& id) = 0;

    virtual std::shared_ptr<iink_surface_model> inkSurface() = 0;

    // load page asynchronously
    // this method can be called on objects created by the user (not loaded from disk)
    // to change the memory foot print.
    virtual void setLoadLevelAsync(const page_level targetLevel) = 0;

    virtual page_level loadLevel() = 0;

    // retrieves the cached thumbnail.
    // this method should be called in response to ipage_model::setLoadLevelAsync(page_level::thumbnail),
    // i.e. in onLoadLevelSet(page_level::thumbnail).
    virtual void thumbnail(s::binary_blob& outBlob) = 0;

    // saves the passed in thumbnail.
    virtual void saveThumbnailAsync(s::binary_blob& inBlob) = 0;

    // call this method before creating a new thumbnail
    // this tells the object that updates after this call may not be included
    // in the thumbnail
    virtual void setThumbnailSnapshotPoint() = 0;

    // call this method to determine whether updates took place
    // since the last time thumbnail was saved or not.
    virtual b::Boolean isThumbnailUpToDate() = 0;

    // call this method to determine if the page has 'linked data'.
    // 'linked data' is everything that might not be available right
    // after the handing out of a ipage object. Instea, it might be
    // available later in response to setPageLoadLevel().
    virtual b::Boolean hasLinkedData() = 0;

    DEBUG_MODEL_INTERFACE_DECLARATION;
};

struct iphoto_model_observer : public b::ibase
{
    BAJA_INTERFACE_NOTAG(iphoto_model_observer);

    virtual void onPhotoMoved(
        const std::shared_ptr<model::iphoto_model>& photo,
        const b::float32& scale,
        const m::point2d& position,
        const b::float32& angle
        ) = 0;
};

struct iphoto_model : public b::ibase
{
    BAJA_INTERFACE_NOTAG(iphoto_model);

    virtual b::int32 addObserver(const std::shared_ptr<iphoto_model_observer>& observer) = 0;
    virtual void removeObserver(const std::shared_ptr<iphoto_model_observer>& observer) = 0;
    virtual void removeObserver(const b::int32 cookie) = 0;

    virtual b::uint32 storageId() = 0;
    virtual std::shared_ptr<ipage_model> parent() = 0;

    virtual std::wstring textureFile() = 0;
    virtual std::shared_ptr<b::imemory_stream> textureFileStream() = 0;
    virtual const b::float32 scale() = 0;
    virtual const m::point2d& position() = 0;
    virtual const b::float32 angle() = 0;

    virtual void setSpatialAttributes(
        const b::float32& scale,
        const m::point2d& position,
        const b::float32& angle
        ) = 0;

    DEBUG_MODEL_INTERFACE_DECLARATION;
};

struct iink_surface_model_observer : public baja::ibase
{
    BAJA_INTERFACE_NOTAG(iink_surface_model_observer);

    virtual void onInkSurfaceModelReady(
        const std::shared_ptr<iink_surface_model>& inkSurface) = 0;

    virtual void onStrokeModelAttached(
        const std::shared_ptr<iink_stroke_model>& newStroke) = 0;

    virtual void onStrokeModelReady(
        const std::shared_ptr<iink_stroke_model>& newStroke) = 0;

    virtual void onStrokeModelRemoved(
        const std::shared_ptr<iink_stroke_model>& stroke) = 0;

    virtual void onInkSurfaceChildrenReady(
        const std::shared_ptr<iink_surface_model>& inkSurface) = 0;
};

struct iink_surface_model : public b::ibase
{
    BAJA_INTERFACE_NOTAG(iink_surface_model);

    virtual b::int32 addObserver(const std::shared_ptr<iink_surface_model_observer>& observer) = 0;
    virtual void removeObserver(const std::shared_ptr<iink_surface_model_observer>& observer) = 0;
    virtual void removeObserver(const b::int32 cookie) = 0;

    virtual b::uint32 storageId() = 0;
    virtual std::shared_ptr<ipage_model> parent() = 0;

    virtual void attachInkStroke(const std::shared_ptr<iink_stroke_model>& stroke) = 0;
    virtual std::shared_ptr<iink_stroke_collection> inkStrokes() = 0;
    virtual std::shared_ptr<iink_stroke_model> createInkStroke() = 0;
    virtual void removeInkStroke(const b::uint32& id) = 0;

    DEBUG_MODEL_INTERFACE_DECLARATION;
};

struct iink_stroke_model : public b::ibase
{
    BAJA_INTERFACE_NOTAG(iink_stroke_model);

    virtual b::uint32 storageId() = 0;
    virtual std::shared_ptr<iink_surface_model> parent() = 0;

    virtual void addPoint(const m::point2d& position, const b::float32 pressure, const b::uint64 t) = 0;
    virtual std::shared_ptr<b::isimple_collection<bi::ink_point>> points() = 0;

    virtual bi::ink_color color() = 0;
    virtual void setColor(const bi::ink_color color) = 0;

    virtual b::float32 width() = 0;
    virtual void setWidth(const b::float32 width) = 0;

    DEBUG_MODEL_INTERFACE_DECLARATION;
};

}} // namespace journal::models
